﻿///<reference path="../../Scripts/typings/node/node.d.ts" />

import fs = require('fs');
import path = require('path');


export class ModuleBundling {

    constructor() {
    }
}


export class ModuleDefinition {

    public moduleDefinitions: IModuleDefinition[];
    public moduleDefinitionsMap: any = {};


    constructor(
        public basePath: string) {

        var ctx = this;

        // get module definitions
        ctx.moduleDefinitions = ctx.getModuleDefinitions();

        // create map
        ctx.moduleDefinitions.forEach(moduleDef => {
            ctx.moduleDefinitionsMap[moduleDef.moduleName] = moduleDef;
        });
    }

    public createModulesFilesRefScripts(): void {
        var ctx = this;

        // foreach module create its definition
        ctx.moduleDefinitions.forEach(modDef => {

            // crate module file ref script
            ctx.createModuleFilesRefScript(modDef);
        });
    }

    public createModuleFilesRefScript(moduleDef: IModuleDefinition): void {
        var ctx = this;

        // module directory
        var moduleDefDirName = path.dirname(moduleDef.filePath);

        // get all module files
        var moduleFiles = ctx.getAllModuleFiles(moduleDef, true);

        // script used to reference all module files
        var script = '';

        // reference module files
        moduleFiles.forEach(moduleFile => {
            
            // take only TS files
            if (/.ts$/i.exec(moduleFile) && !/module.ref.ts$/i.exec(moduleFile)) {
                script += '\n///<reference path="' + moduleFile + '" />';
            }
        });

        // reference dependencies
        if (moduleDef.dependencies && moduleDef.dependencies.length > 0) {

            script += '\n';

            moduleDef.dependencies.forEach(dep => {

                // get dependency
                var depModDef = <IModuleDefinition>ctx.moduleDefinitionsMap[dep];

                if (depModDef) {
                    script += '\n///<reference path="' + path.relative(moduleDefDirName, path.join(path.dirname(depModDef.filePath), 'module.ref.ts')) + '" />';
                }
                else {
                    console.warn('Module \'' + moduleDef.moduleName + '\' :: Dependency \'' + dep + '\' was not found.');
                }
            });
        }

        var moduleRefFilePath = path.join(moduleDefDirName, 'module.ref.ts');

        fs.writeFileSync(moduleRefFilePath, script);

        console.log('\nmodule.ref.ts created at: ' + moduleRefFilePath);
    }

    private getModuleDefinitions(): IModuleDefinition[] {
        var ctx = this;

        var moduleDefLst: IModuleDefinition[] = [];

        // get all files
        var files = ctx.getAllFiles(ctx.basePath);

        // create regex to identify the module
        var moduleFileRegex = new RegExp("([^\\\\]*)\\\\(([^\\.\\\\])+\\.)*module\\.json");

        files.forEach(filePath => {

            var match = moduleFileRegex.exec(filePath);

            if (match) {

                // get module name from regex
                var folderModuleName = match[1];
                var fileModuleName = match[3];
                var moduleName = fileModuleName ? fileModuleName : folderModuleName;
                
                // read file
                var moduleDefJson = fs.readFileSync(filePath);

                var moduleDef: IModuleDefinition = null;

                // json to object
                eval('moduleDef = ' + moduleDefJson);

                moduleDef.moduleName = moduleName;
                moduleDef.filePath = filePath;

                // add module
                moduleDefLst.push(moduleDef);
            }
        });

        return moduleDefLst;
    }    

    private getAllModuleFiles(moduleDef: IModuleDefinition, relativePaths: boolean): string[] {
        var ctx = this;

        // get module.json file directory
        var moduleDir = path.dirname(moduleDef.filePath);

        // get all files in directory
        var moduleFiles = ctx.getAllFiles(moduleDir);

        // if relative paths is selected then create relative version of every of the module files paths
        if (relativePaths) {
            var relativeModuleFiles = [];

            moduleFiles.forEach(moduleFile => {
                relativeModuleFiles.push(path.relative(moduleDir, moduleFile));
            });

            moduleFiles = relativeModuleFiles;
        }

        return moduleFiles;
    }    

    private getAllFiles(basePath): string[] {
        var ctx = this;

        var files: string[] = [];

        // get all files
        var allFiles = fs.readdirSync(basePath);

        // foreach file check if it is a directory and if is then add all its files
        if (allFiles && allFiles.length > 0) {
            allFiles.forEach(fileName => {

                var filePath = path.resolve(path.join(basePath, fileName));

                // determine if is a directory
                if (fs.statSync(filePath).isDirectory()) {
                    
                    // get all directory files
                    var dirFiles = ctx.getAllFiles(filePath);

                    // add files
                    files.push.apply(files, dirFiles);
                }
                else {
                    files.push(filePath);
                }
            });
        }

        return files;
    }    
}


export interface IModuleDefinition {    
    moduleName: string;
    filePath: string;
    dependencies: string[];
    scripts: string[]
}